package proxyc

import (
	"bytes"
	"context"
	"errors"
	"gitee.com/yysf-xin/dproxy/pkg/app"
	"gitee.com/yysf-xin/dproxy/pkg/db"
	"gitee.com/yysf-xin/dproxy/pkg/model"
	"gitee.com/yysf-xin/dproxy/pkg/rest"
	"gitee.com/yysf-xin/dproxy/pkg/tools"
	"gitee.com/yysf-xin/dproxy/pkg/turn"
	"github.com/go-chi/chi/v5"
	"golang.org/x/net/proxy"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"
)

type turnHolder struct {
	client *turn.Client
	done   chan struct{}
	conn   net.Conn
	stop   bool
}

func (h *turnHolder) serve() {
	h.stop = false
server:
	err := h.client.Serve(h.conn)
	if h.stop {
		goto end
	}
	tools.Log.Println("turn err", err)
	for {
		select {
		case _, _ = <-h.done:
			goto end
		case <-time.After(10 * time.Second):
			err = h.connect()
			if err == nil {
				goto server
			}
		}
	}
end:
	h.stop = true
	tools.Log.Println("turn stopped", err)
}

func (h *turnHolder) connect() (err error) {
	h.conn, err = h.client.DialHTTP(http.MethodPut, "")
	return
}

type TurnControl struct {
	services      *app.Services
	server        *turn.Server
	clientMap     map[uint]*turnHolder
	groupMap      map[uint]*db.TurnGroup
	groupMapLock  sync.RWMutex
	DB            *db.Client
	Enabled       bool
	clientMapLock sync.RWMutex
	dialer        proxy.Dialer
}

var Err = errors.New("not enabled")

func (t *TurnControl) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
	if t.Enabled {
		return t.server.DialContext(ctx, network, address)
	} else {
		return nil, Err
	}
}

func NewTurnControl(services *app.Services, DB *db.Client, c turn.GroupChooser, dialer proxy.Dialer) *TurnControl {
	return &TurnControl{
		services:  services,
		server:    turn.NewServer(c),
		clientMap: make(map[uint]*turnHolder),
		groupMap:  make(map[uint]*db.TurnGroup),
		Enabled:   true,
		DB:        DB,
		dialer:    dialer,
	}
}

func (t *TurnControl) String() string {
	return t.server.String()
}

func (t *TurnControl) InitRoute(r chi.Router) {
	r.Post("/enable", app.JsonHttp(func() interface{} {
		return &model.Enable{}
	}, t.handlerEnable))

	r.Get("/clients", app.Json(nil, t.listClient))

	t.services.AddService(t)
}

func (t *TurnControl) InitTurnRoute(r chi.Router) {
	r.Get("/{id}", t.server.ServeHTTP)
	r.Get("/{id}/*", t.server.ServeHTTP)
}

func (t *TurnControl) handlerEnable(_ http.ResponseWriter, _ *http.Request, i interface{}) (rs interface{}, err error) {
	t.Enabled = i.(*model.Enable).Enabled
	return
}

type TurnClient struct {
	Id    string
	SrcIp string
	Time  int64
}

func (t *TurnControl) listClient(i interface{}) (interface{}, error) {
	var (
		list = make([]*TurnClient, 0)
		Id   uint64
		buf  bytes.Buffer
	)
	for k, l := range t.server.GroupMap {
		Id = 0
		for e := l.Front(); e != nil; e = e.Next() {
			buf.Reset()
			Id++
			buf.WriteString(strconv.FormatUint(k, 10))
			buf.WriteString("_")
			buf.WriteString(strconv.FormatUint(Id, 10))

			c := e.Value.(*turn.ClientCodec)
			list = append(list, &TurnClient{
				Id:    buf.String(),
				SrcIp: c.Conn.RemoteAddr().String(),
				Time:  c.Time,
			})
		}
	}
	return list, nil
}

func (t *TurnControl) InitClient(router chi.Router) {
	router.Get("/", app.Json(nil, t.list))
	router.Route("/{id}/", func(r chi.Router) {
		r.Post("/enable", app.JsonHttp(func() interface{} {
			return &model.Enable{}
		}, t.handlerClientEnable))
	})
	router.Delete("/{id}", app.JsonHttp(nil, t.DeleteClient))

	t.reloadDB()
}
func (t *TurnControl) reloadDB() {
	var (
		list []*db.TurnClient
		err  error
	)
	err = t.DB.Db.Find(&list, "enabled = ?", true).Error
	if err != nil {
		tools.Log.Println("read turn err", err)
		return
	}
	for i := range list {
		m := list[i]
		go func() {
			err = t.startClient(m)
			if err != nil {
				tools.Log.Println("turn client", m.Endpoint, err)
			}
		}()
	}
}
func (t *TurnControl) list(i interface{}) (rs interface{}, err error) {
	var list []*db.TurnClient
	err = t.DB.Db.Find(&list).Error

	if err != nil {
		return
	}
	t.clientMapLock.RLock()
	for i2 := range list {
		c := t.clientMap[list[i2].Id]
		if c != nil && !c.stop {
			list[i2].Status = 1
		}
	}
	t.clientMapLock.RUnlock()
	rs = &list
	return
}

func (t *TurnControl) handlerClientEnable(_ http.ResponseWriter, r *http.Request, i interface{}) (rs interface{}, err error) {
	var m db.TurnClient
	err = rest.FindModelByReq(r, &m, t.DB.Db)
	if err != nil {
		return
	}
	if i.(*model.Enable).Enabled {
		err = t.startClient(&m)
	} else {
		err = t.stopClient(&m)
	}
	return
}
func (t *TurnControl) DeleteClient(_ http.ResponseWriter, r *http.Request, i interface{}) (rs interface{}, err error) {
	var m db.TurnClient
	err = rest.FindModelByReq(r, &m, t.DB.Db)
	if err != nil {
		return
	}
	err = t.stopClient(&m)
	return
}

func (t *TurnControl) startClient(m *db.TurnClient) (err error) {
	err = t.stopClient(m)
	if err == nil && m.EndpointURL == nil {
		m.EndpointURL, err = url.Parse(m.Endpoint)
		if !strings.Contains(m.EndpointURL.Host, ":") {
			if m.EndpointURL.Scheme == "http" {
				m.EndpointURL.Host = m.EndpointURL.Host + ":80"
			}
		}
	}
	if err != nil {
		return
	}
	c, err := turn.NewClient(m.EndpointURL.Host, m.EndpointURL.Path, t.dialer)
	h := &turnHolder{
		client: c,
		done:   make(chan struct{}),
	}
	err = h.connect()
	if err != nil {
		return
	}
	go h.serve()
	t.clientMapLock.Lock()
	t.clientMap[m.Id] = h
	t.clientMapLock.Unlock()
	return
}

func (t *TurnControl) stopClient(d *db.TurnClient) (err error) {
	t.clientMapLock.Lock()
	c := t.clientMap[d.Id]
	if c != nil {
		c.stop = true
		err = c.client.Close()
		close(c.done)
		delete(t.clientMap, d.Id)
	}
	t.clientMapLock.Unlock()
	return
}

func (t *TurnControl) InitTurnGroup(route chi.Router) {
	route.Route("/{id}/", func(r chi.Router) {
		r.Post("/enable", app.JsonHttp(func() interface{} {
			return &model.Enable{}
		}, t.handlerGroup))
	})
	var list []*db.TurnGroup
	t.DB.Db.Find(&list, "enabled = ?", true)

	for i := range list {
		t.addTurnGroup(list[i])
	}
}

func (t *TurnControl) handlerGroup(_ http.ResponseWriter, r *http.Request, i interface{}) (rs interface{}, err error) {
	var m db.TurnGroup
	err = rest.FindModelByReq(r, &m, t.DB.Db)
	if err != nil {
		return
	}
	t.DB.Db.Model(&m).Update("enabled", i.(*model.Enable).Enabled)
	if i.(*model.Enable).Enabled {
		t.addTurnGroup(&m)
		rs = &m
	} else {
		t.removeTurnGroup(&m)
	}
	return
}

func (t *TurnControl) addTurnGroup(d *db.TurnGroup) {
	t.groupMapLock.Lock()
	t.groupMap[d.Id] = d
	t.groupMapLock.Unlock()
}

func (t *TurnControl) removeTurnGroup(d *db.TurnGroup) {
	t.groupMapLock.Lock()
	delete(t.groupMap, d.Id)
	t.groupMapLock.Unlock()
}
